<?php
	
	/* dependence */
	CoreProject::Using('core.socket');

	/**
	 * @package		CoreProject
	 * @subpackage	Libs
	 *
	 * cpr_Libs_Pop3Config
	 * cpr_Libs_Pop3
	 * 
	 */

	class cpr_Libs_Pop3Config
	{
		/**
		 * @var	string
		 */
		private $_pop3Login;

		/**
		 * @var	string
		 */
		private $_pop3Password;

		/**
		 * @param	string	$_login
		 * @param	string	$_password
		 * @return	cpr_Libs_Pop3Config
		 */
		public function  __construct($_login, $_password)
		{
			$this->_pop3Login = $_login;
			$this->_pop3Password = $_password;
		}

		/**
		 * @return	string
		 */
		public function Login()
		{
			return $this->_pop3Login;
		}

		/**
		 * @return	string
		 */
		public function Password()
		{
			return $this->_pop3Password;
		}
	}

	class cpr_Libs_Pop3 extends cpr_Core_Socket
	{
		const LOG_CLASSNAME = 'pop3';
		
		/**
		 * @var	cpr_Libs_Pop3Config
		 */
		private $_pop3Config;

		/**
		 * @var	string
		 */
		private $_apop_banner = '';

		/**
		 * @param	cpr_Core_SocketConfig	$_config
		 * @param	cpr_Libs_Pop3Config		$_pop3Config = null
		 */
		public function __construct($_socketConfig, $_pop3Config = null)
		{
			parent::__construct($_socketConfig);
			$this->_pop3Config = $_pop3Config;
		}

		/**
		 * @return	bool
		 */
		public function Connect()
		{
			return parent::Connect(self::LOG_CLASSNAME) && $this->_isSuccess() && $this->_parseBanner($this->LastBuffer());
		}

		/**
		 * @return	bool
		 */
		public function Login()
		{
			if (strlen($this->_apop_banner) > 0)
			{
				if (!$this->_executeCommand('APOP '.$this->_pop3Config->Login().' '.md5($this->_apop_banner.$this->_pop3Config->Password())))
				{
					return false;
				}

				return true;
			}
			else
			{
				if (!$this->_executeCommand('USER '.$this->_pop3Config->Login()) ||
						!$this->_executeCommand('PASS '.$this->_pop3Config->Password()))
				{
					return false;
				}

				return true;
			}
		}

		/**
		 * @return	bool
		 */
		public function ConnectAndLogin()
		{
			return $this->Connect() && $this->Login();
		}

		/**
		 * @return	bool
		 */
		public function Disconnect()
		{
			return $this->_executeCommand('QUIT') && parent::Disconnect();
		}

		function  __destruct()
		{
			$this->Disconnect();
		}

		/**
		 * @return	bool
		 */
		public function Noop()
		{
			return $this->_executeCommand('NOOP');
		}

		/**
		 * @return	bool
		 */
		public function Rset()
		{
			return $this->_executeCommand('RSET');
		}


		/**
		 * @param	int	$index
		 * @return	bool
		 */
		public function DeleteMessage($index)
		{
			return $this->_executeCommand('DELE '.$index);
		}

		/**
		 * @param	array	&$resp
		 * @param	int		$index = null
		 * @return	bool
		 */
		public function Uidl(&$resp, $index = null)
		{
			$resp = array();
			if (null !== $index && $index > 0)
			{
				if (!$this->_executeCommand('UIDL '.$index))
				{
					return false;
				}

				$_last = $this->LastBuffer();
				$infoArray = explode(' ', $_last);
				if (count($infoArray) > 2)
				{
					$resp[(int) $infoArray[1]] = $infoArray[2];
				}
				return true;
			}
			else
			{
				if (!$this->_executeCommand('UIDL'))
				{
					return false;
				}

				$_line = '';
				do
				{
					$_line = $this->getLine(self::LOG_CLASSNAME);
					if (false === $_line || null === $_line)
					{
						return false;
					}

					$_line = trim($_line);
					$_match = array();
					preg_match('/([\d]+) (.+)$/', $_line, $_match);
					if (count($_match) == 3)
					{
						$resp[(int) $_match[1]] = $_match[2];
					}
				}
				while ($_line !== '.' || $_line === '');

				return true;
			}
			return false;
		}

		/**
		 * @param	array	&$resp
		 * @param	int		$index = null
		 * @return	bool
		 */
		public function Sizes(&$resp, $index = null)
		{
			$resp = array();
			if (null !== $index && $index > 0)
			{
				if (!$this->_executeCommand('LIST '.$index))
				{
					return false;
				}

				$_last = $this->LastBuffer();
				$infoArray = explode(' ', $_last);
				if (count($infoArray) > 2)
				{
					$resp[(int) $infoArray[1]] = (int) $infoArray[2];
				}
				return true;
			}
			else
			{
				if (!$this->_executeCommand('LIST'))
				{
					return false;
				}

				$_line = '';
				do
				{
					$_line = $this->getLine(self::LOG_CLASSNAME);
					if (false === $_line || null === $_line)
					{
						return false;
					}

					$_line = trim($_line);
					$_match = array();
					preg_match('/([\d]+) ([\d]+)$/', $_line, $_match);
					if (count($_match) == 3)
					{
						$resp[(int) $_match[1]] = (int) $_match[2];
					}
				}
				while ($_line !== '.' || $_line === '');

				return true;
			}
			return false;
		}

		/**
		 * @param	string	&$text
		 * @param	int		$index
		 * @param	int		$lines = 0
		 * @return	bool
		 */
		public function GetTop(&$text, $index, $lines = 0)
		{
			if (!$this->_executeCommand('TOP '.$index.' '.$lines))
			{
				return false;
			}

			return $this->_getLines($text);
		}

		/**
		 * @param	string	&$text
		 * @param	int		$index
		 * @return	bool
		 */
		public function GetMsg(&$text, $index)
		{
			if (!$this->_executeCommand('RETR '.$index))
			{
				return false;
			}

			return $this->_getLines($text);
		}

		/**
		 * @param	int	$count
		 * @param	int	$size
		 * @return	bool
		 */
		public function Stat(&$count, &$size)
		{
			if (!$this->_executeCommand('STAT'))
			{
				return false;
			}

			/*
			 * Some server send the STAT string is finished by '.' ('+OK 3 52422.' - Yahoo Server)
			 */
			$_last = $this->LastBuffer();
			if (!preg_match('/[0-9]/', substr($_last, -1)))
			{
				$_last = substr($_last, 0, -1);
			}

			if (preg_match('/\+OK [0-9]+ [0-9]+/', $_last))
			{
				$infoArray = explode(' ', $_last);
				if (3 == count($infoArray))
				{
					$count = (int) $infoArray[1];
					$size = (int) $infoArray[2];
				}
				unset($infoArray);
				return true;
			}
			return false;
		}

		/**
		 * @param	string	$_command
		 * @return	bool
		 */
		private function _executeCommand($_command)
		{
			if (!$this->IsConnect())
			{
				return false;
			}

			return $this->putLine($_command, self::LOG_CLASSNAME) && $this->_isSuccess();
		}

		/**
		 * @param	string	&$text
		 * @param	bool	$isLog = true
		 * @return	bool
		 */
		private function _getLines(&$text, $isLog = true)
		{
			$_lines = array();
			while (true)
			{
				$_line = $this->getLine(self::LOG_CLASSNAME, $isLog);
				if (false === $_line)
				{
					return false;
				}
				else if (substr($_line, 0, 3) != '.'.CPR_CRLF)
				{
					$_lines[] = $_line;
				}
				else
				{
					break;
				}
			}

			$text = implode('', $_lines);
			return true;
		}
		
		/**
		 * @return	bool
		 */
		private function _isSuccess()
		{
			$_result = false;

			$_line = $this->getLine(self::LOG_CLASSNAME);
			if (false === $_line)
			{
				$this->setError(self::LOG_CLASSNAME, 'false response.');
				$this->cleanUp();
			}
			else
			{
				if (substr($_line, 0, 3) === '+OK')
				{
					$_result = true;
				}
				else
				{
					$_result = false;
					$this->setError(self::LOG_CLASSNAME, 'response not success ('.$_line.').');
					$this->cleanUp();
				}
			}

			return $_result;
		}

		/**
		 * @param	string	$server_text
		 * @return	true
		 */
		private function _parseBanner($server_text)
		{
			$outside = true;
			$banner = '';
			$length = strlen($server_text);
			for ($count = 0; $count < $length; $count++)
			{
				$digit = substr($server_text, $count, 1);
				if ($digit != '')
				{
					if (!$outside && $digit != '<' && $digit != '>')
					{
						$banner .= $digit;
						continue;
					}
					if ($digit == '<')
					{
						$outside = false;
					}
					else if ($digit == '>')
					{
						$outside = true;
					}
				}
			}

			$banner = trim($banner);
			if (strlen($banner) != 0)
			{
				$this->_apop_banner = '<'. $banner .'>';
			}
		
			return true;
		}
	}
